# MediaCacheManager - Downloads metadata and keeps media cache up-to-date
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import os
import sys

from utils.configuration import Configuration
from backend.core.message_type_priority import MessageType
from backend.core.message_handler import MessageHandler
from backend.components.mediacache.indexer_thread import IndexerThread
from backend.components.mediacache.file_system_observer import FileSystemObserver
from backend.components.mediacache.image_cache import ImageCache
from backend.components.mediacache.music_cache import MusicCache
from backend.components.mediacache.video_cache import VideoCache

class MediaCacheManager(MessageHandler):
    """MediaCacheManager makes sure that frontend has all the data available."""
    
    def __init__(self, logger, configuration):
        """
        Create a new MediaCacheManager object
        @param logger: Logger object
        @param configuration: Configuration object
        """
        self.logger = logger
        self.configuration = configuration
        self.video_folders = self.configuration.get_video_folders()
        self.music_folders = self.configuration.get_music_folders()
        self.image_folders = self.configuration.get_image_folders()

        # Start observing file system changes
        self.fs_observer = FileSystemObserver(self.logger, self.configuration)
        for element in self.image_folders:
            self.fs_observer.addFolder(element, "image")
        for element in self.music_folders:
            self.fs_observer.addFolder(element, "music")
        for element in self.video_folders:
            self.fs_observer.addFolder(element, "video")
        self.fs_observer.startObserving()

        # Should we rebuild to detect files that were removed while backend was
        # not running?! THERE HAS TO BE A BETTER SOLUTION FOR THIS PROBLEM
        self.rebuildAllMediaCaches()
        
    def rebuildAllMediaCaches(self):
        """Rebuilds all media caches."""
        self.rebuildImageCache()
        self.rebuildMusicCache()
        self.rebuildVideoCache()
        
    def rebuildVideoCache(self):
        """Destroy all current data and index everything from the scratch."""
        for element in self.video_folders:
            self.fs_observer.removeFolder(element)
        for element in self.video_folders:
            self.fs_observer.addFolder(element, "video")
        self.logger.info("Video cache rebuilding requested")
        video_cache = VideoCache(self.logger, self.configuration)
        video_cache.clearCache()
        self._index_videos(self.video_folders)
        
    def rebuildMusicCache(self):
        """Destroy all current data and index everything from the scratch."""
        for element in self.music_folders:
            self.fs_observer.removeFolder(element)
        for element in self.music_folders:
            self.fs_observer.addFolder(element, "music")
        self.logger.info("Music cache rebuilding requested")
        music_cache = MusicCache(self.logger, self.configuration)
        music_cache.clearCache()
        self._index_music(self.music_folders)
        
    def rebuildImageCache(self):
        """Destroy all current data and index everything from the scratch."""
        for element in self.image_folders:
            self.fs_observer.removeFolder(element)
        for element in self.image_folders:
            self.fs_observer.addFolder(element, "image")
        self.logger.info("Image cache rebuilding requested")
        image_cache = ImageCache(self.logger, self.configuration)
        image_cache.clearCache()
        self._index_images(self.image_folders)
        
    # Implements MessageHandler interface
    def handleMessage(self, message):
        if message.get_type() == MessageType.CONTENT_CONF_UPDATED:
            self._update_content_folders()
        elif message.get_type() == MessageType.REBUILD_VIDEO_CACHE:
            self.rebuildVideoCache()
        elif message.get_type() == MessageType.REBUILD_MUSIC_CACHE:
            self.rebuildMusicCache()
        elif message.get_type() == MessageType.REBUILD_IMAGE_CACHE:
            self.rebuildImageCache()
        
    def getName(self):
        return "MediaCacheManager"
        
# ------------------------------------------------------------------------------
# PRIVATE METHODS ARE BELOW                                                     
# ------------------------------------------------------------------------------

    def _index_images(self, folders):
        """Index images from the given folders and their subfolders"""
        if len(folders) > 0:
            indexer = IndexerThread(self.logger, self.configuration)
            indexer.setCacheType("image")
            indexer.setFolders(folders)
            indexer.start()
        
    def _index_music(self, folders):
        """Index music from the given folders and their subfolders"""
        if len(folders) > 0:
            indexer = IndexerThread(self.logger, self.configuration)
            indexer.setCacheType("music")
            indexer.setFolders(folders)
            indexer.start()
        
    def _index_videos(self, folders):
        """Index videos from the given folders and their subfolders"""
        if len(folders) > 0:
            indexer = IndexerThread(self.logger, self.configuration)
            indexer.setCacheType("video")
            indexer.setFolders(folders)
            indexer.start()
        
    def _update_content_folders(self):
        """
        This updates media manager's content folders. This method is
        executed when content.conf has been updated. If folders are added
        we need to index them. If folders are removed, we need to remove
        them from the cache and also from FileSystemObeserver.
        """
        updated_video_folders = self.configuration.getVideoFolders()
        updated_music_folders = self.configuration.getMusicFolders()
        updated_image_folders = self.configuration.getImageFolders()
             
        # Handle image folder changes
        # ----------------------------------------------------------------------
        current_images = set(self.image_folders)
        updated_images = set(updated_image_folders)
        removed_images = current_images - updated_images
        new_images = updated_images - current_images
        self.image_folders = updated_image_folders
        
        # Remove folders from cache and fs_observer
        for element in removed_images:
            self.fs_observer.removeFolder(element)
            
        image_cache = ImageCache(self.logger)
        for element in removed_images:
            image_cache.removeDirectory(element)
            
        # Add new paths to fs_observer and cache
        for element in new_images:
            self.fs_observer.addFolder(element, "image")
        self._index_images(list(new_images))
        
        # Handle music folder changes
        # ----------------------------------------------------------------------
        current_music = set(self.music_folders)
        updated_music = set(updated_music_folders)
        removed_music = current_music - updated_music
        new_music = updated_music - current_music
        self.music_folders = updated_music_folders
        
        # Remove folders from cache and fs_observer
        for element in removed_music:
            self.fs_observer.removeFolder(element)
            
        music_cache = MusicCache(self.logger)
        for element in removed_music:
            music_cache.removeDirectory(element)
        
        # Add new paths to fs_observer and cache
        for element in new_music:
            self.fs_observer.addFolder(element, "music")
        self._index_music(list(new_music))
        
        # Handle video folder changes
        # ----------------------------------------------------------------------
        current_videos = set(self.video_folders)
        updated_videos = set(updated_video_folders)
        removed_videos = current_videos - updated_videos
        new_videos = updated_videos - current_videos
        self.video_folders = updated_video_folders
        
        # Remove folders from cache and fs_observer
        for element in removed_videos:
            self.fs_observer.removeFolder(element)
            
        video_cache = VideoCache(self.logger)
        for element in removed_videos:
            video_cache.removeDirectory(element)
        
        # Add new paths to fs_observer and cache
        for element in new_videos:
            self.fs_observer.addFolder(element, "video")
        self._index_videos(list(new_videos))
